<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../../../resources/js-test.js"></script>
<title></title>
</head>
<body>
<p id=description></p>
<div id="console"></div>
<script>

window.jsTestIsAsync = true;
var mutations, mutations2;
var calls;
var div, removedDiv1, removedDiv2, addedDiv1, addedDiv2, addedDiv3;

function testBasic() {
    var div;
    var observer;

    function start() {
        debug('Testing basic aspects of childList observation.');

        mutations = null;
        div = document.createElement('div');
        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        observer.observe(div, { childList: true });
        removedDiv1 = div.appendChild(document.createElement('div'));
        setTimeout(checkDisconnectAndMutate, 0);
    }

    function checkDisconnectAndMutate() {
        debug('...can childList changes be observed at all');

        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations[0].addedNodes.length', '1');
        shouldBe('mutations[0].addedNodes[0]', 'removedDiv1');

        mutations = null;
        observer.disconnect();
        removedDiv1 = div.appendChild(document.createElement('div'));
        setTimeout(checkNotDeliveredAndMutateMultiple, 0);
    }

    function checkNotDeliveredAndMutateMultiple() {
        debug('...observer.disconnect() should prevent further delivery of mutations.');

        shouldBe('mutations', 'null');
        observer.observe(div, { childList: true });
        removedDiv1 = div.removeChild(div.firstChild);
        removedDiv2 = div.removeChild(div.firstChild);
        setTimeout(finish);
    }

    function finish() {
        debug('...re-observing after disconnect works with the same observer.');

        shouldBe('mutations.length', '2');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations[0].removedNodes.length', '1');
        shouldBe('mutations[0].removedNodes[0]', 'removedDiv1');
        shouldBe('mutations[1].type', '"childList"');
        shouldBe('mutations[1].removedNodes.length', '1');
        shouldBe('mutations[1].removedNodes[0]', 'removedDiv2');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testWrongType() {
    var div;
    var observer;

    function start() {
        debug('Testing that observing without specifying "childList" does not result in hearing about childList changes.');

        mutations = null;
        div = document.createElement('div');
        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        observer.observe(div, { attributes: true, characterData: true });
        div.appendChild(document.createElement('div'));
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations', 'null');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testMultipleRegistration() {
    var div;
    var observer;

    function start() {
        debug('Testing that re-observing the same node with the same observer has the effect of resetting the options.');

        calls = 0;
        mutations = null;
        div = document.createElement('div');
        observer = new MutationObserver(function(m) {
            mutations = m;
            calls++;
        });

        observer.observe(div, { childList: true, characterData: true });
        observer.observe(div, { childList: true });
        div.appendChild(document.createElement('div'));
        setTimeout(checkDisconnectAndMutate, 0);
    }

    function checkDisconnectAndMutate() {
        shouldBe('calls', '1');
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        mutations = null;
        observer.observe(div, { childList: true, characterData: true });
        observer.observe(div, { attributes: true });
        div.appendChild(document.createElement('div'));
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations', 'null');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testMultipleObservers() {
    var div;
    var observer;
    var observer2;

    function start() {
        debug('Testing that multiple observers can be registered to a given node and both receive mutations.');
        mutations = null;
        div = document.createElement('div');
        observer = new MutationObserver(function(m) {
            mutations = m;
        });
        observer2 = new MutationObserver(function(m) {
            mutations2 = m;
        });
        observer.observe(div, { childList: true });
        observer2.observe(div, { childList: true });
        div.appendChild(document.createElement('div'));
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations2.length', '1');
        shouldBe('mutations2[0].type', '"childList"');
        observer.disconnect();
        observer2.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testInnerHTMLAndInnerText() {
    var div;
    var observer;

    function start() {
        debug('Testing that innerText and innerHTML always result in a single childList mutation.');

        mutations = null;
        div = document.createElement('div');
        div.innerHTML = '<span>Foo</span><div>Bar</div>';
        removedDiv1 = div.firstChild;
        removedDiv2 = removedDiv1.nextSibling;
        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        observer.observe(div, { childList: true });
        div.innerHTML = 'foo<img src="bar.png"><p>Baz</p>';
        addedDiv1 = div.childNodes[0];
        addedDiv2 = div.childNodes[1];
        addedDiv3 = div.childNodes[2];
        setTimeout(checkInnerHTML, 0);
    }

    function checkInnerHTML() {
        debug('...innerHTML');

        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations[0].addedNodes.length', '3');
        shouldBe('mutations[0].addedNodes[0]', 'addedDiv1');
        shouldBe('mutations[0].addedNodes[1]', 'addedDiv2');
        shouldBe('mutations[0].addedNodes[2]', 'addedDiv3');
        shouldBe('mutations[0].removedNodes.length', '2');
        shouldBe('mutations[0].removedNodes[0]', 'removedDiv1');
        shouldBe('mutations[0].removedNodes[1]', 'removedDiv2');

        mutations = null;
        div.innerText = 'foo';
        setTimeout(finish, 0);
    }

    function finish() {
        debug('...innerText');

        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations[0].addedNodes.length', '1');
        shouldBe('mutations[0].removedNodes.length', '3');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testReplaceChild() {
    var div;
    var observer;
    var fragment;

    function start() {
        debug('Testing that replaceChild results in minimal childList mutations.');

        mutations = null;
        div = document.createElement('div');
        div.innerHTML = '<span>Foo</span><div>Bar</div>';
        removedDiv1 = div.firstChild;

        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        observer.observe(div, { childList: true });
        addedDiv1 = document.createElement('div');
        div.replaceChild(addedDiv1, div.firstChild);
        setTimeout(checkReplaceWithNode, 0);
    }

    function checkReplaceWithNode() {
        debug('...simple replace child');

        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations[0].addedNodes.length', '1');
        shouldBe('mutations[0].addedNodes[0]', 'addedDiv1');
        shouldBe('mutations[0].removedNodes.length', '1');
        shouldBe('mutations[0].removedNodes[0]', 'removedDiv1');

        mutations = null;
        fragment = document.createDocumentFragment();
        addedDiv1 = fragment.appendChild(document.createElement('div'));
        addedDiv2 = fragment.appendChild(document.createElement('div'));
        removedDiv1 = div.firstChild;

        div.replaceChild(fragment, removedDiv1);

        setTimeout(finish, 0);
    }

    function finish() {
        debug('...replace with DocumentFragment');

        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations[0].addedNodes.length', '2');
        shouldBe('mutations[0].addedNodes[0]', 'addedDiv1');
        shouldBe('mutations[0].addedNodes[1]', 'addedDiv2');
        shouldBe('mutations[0].removedNodes.length', '1');
        shouldBe('mutations[0].removedNodes[0]', 'removedDiv1');

        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testInsertBefore() {
    var div;
    var observer;
    var fragment;

    function start() {
        debug('Testing that insertBefore results in minimal childList mutations.');

        mutations = null;
        div = document.createElement('div');
        div.innerHTML = '<span>Foo</span>';
        fragment = document.createDocumentFragment();
        addedDiv1 = fragment.appendChild(document.createElement('div'));
        addedDiv2 = fragment.appendChild(document.createElement('div'));

        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        observer.observe(div, { childList: true });
        div.insertBefore(fragment, div.firstChild);
        setTimeout(finish, 0);
    }


    function finish() {
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations[0].addedNodes.length', '2');
        shouldBe('mutations[0].addedNodes[0]', 'addedDiv1');
        shouldBe('mutations[0].addedNodes[1]', 'addedDiv2');
        shouldBe('mutations[0].removedNodes.length', '0');

        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testAppendChild() {
    var div;
    var observer;
    var fragment;

    function start() {
        debug('Testing that appendChild results in minimal childList mutations.');

        mutations = null;
        div = document.createElement('div');
        div.innerHTML = '<span>Foo</span>';
        fragment = document.createDocumentFragment();
        addedDiv1 = fragment.appendChild(document.createElement('div'));
        addedDiv2 = fragment.appendChild(document.createElement('div'));

        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        observer.observe(div, { childList: true });
        div.appendChild(fragment);
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"childList"');
        shouldBe('mutations[0].addedNodes.length', '2');
        shouldBe('mutations[0].addedNodes[0]', 'addedDiv1');
        shouldBe('mutations[0].addedNodes[1]', 'addedDiv2');
        shouldBe('mutations[0].removedNodes.length', '0');

        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testInnerHTMLEmpty() {
    function start() {
        debug('Setting an empty childlist to the empty string with innerHTML should not assert.');

        var div = document.createElement('div');
        mutations = null;
        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
        });
        observer.observe(div, {childList: true});
        div.innerHTML = '';
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBeNull('mutations');
        debug('');
        runNextTest();
    }

    start();
}

var tests = [testBasic, testWrongType, testMultipleRegistration, testMultipleObservers, testInnerHTMLAndInnerText, testReplaceChild, testInsertBefore, testAppendChild, testInnerHTMLEmpty];
var testIndex = 0;
 
function runNextTest() {
    if (testIndex < tests.length)
        tests[testIndex++]();
    else
        finishJSTest();
}

description('Test WebKitMutationObserver.observe on attributes.');

runNextTest();
</script>
</body>
</html>
